//-----------------------------------------------------------------------------
// ScreenManager.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
using System;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Ferpect.GameComponents;
using Ferpect.GameComponents.Design;

namespace Ferpect.GameState
{
    /// <summary>
    /// The screen manager is a component which manages one or more GameScreen
    /// instances. It maintains a stack of screens, calls their Update and Draw
    /// methods at the appropriate times, and automatically routes input to the
    /// topmost active screen.
    /// </summary>
    [DesignerSerializer(ScreenManager.ScreenManagerTypeSerializer, typeof(TypeCodeDomSerializer))]
    [DesignerSerializer(ScreenManager.ScreenManagerControlSerializer, typeof(CodeDomSerializer))]
    //[ViewDocumentDesigner(ScreenManager.ScreenManagerDesigner)]
    public class ScreenManager : ViewComponent
    {
        private List<GameScreen> screens = new List<GameScreen>();
        private List<GameScreen> screensToUpdate = new List<GameScreen>();

        private InputState input = new InputState();

        private SpriteBatch spriteBatch;
        private SpriteFont font;
        private Texture2D blankTexture;
        private Game game;
        private bool isInitialized;

        internal const string ScreenManagerTypeSerializer = "Ferpect.GameComponents.Design.Serialization.ViewTypeCodeDomSerializer`1[[Ferpect.GameState.Design.ScreenManagerControl, " + AssemblyRefs.GameStateDesign + "]]";
        internal const string ScreenManagerControlSerializer = "Ferpect.GameComponents.Design.Serialization.ViewControlCodeDomSerializer`2[[Ferpect.GameState.Design.ScreenManagerControl, " + AssemblyRefs.GameStateDesign + "], [Ferpect.GameState.ScreenManager, " + AssemblyRefs.GameState + "]]";
        internal const string ScreenManagerDesigner = "Ferpect.GameState.Design.ScreenManagerDesigner, " + AssemblyRefs.GameStateDesign;

        /// <summary>
        /// Constructs a new screen manager component.
        /// </summary>
        public ScreenManager(Game game)
        {
            if (game == null) throw new ArgumentNullException();
            this.game = game;
        }

        /// <summary>
        /// CONSIDER: This should only be used by the designer.
        /// </summary>
        public ScreenManager()
        {
        }

        #region Properties

        public Game Game
        {
            get { return game; }
        }

        public bool IsActive
        {
            get
            {
                if (this.game == null)
                {
                    return true;
                }
                return this.game.IsActive;
            }
        }
	
        /// <summary>
        /// A default SpriteBatch shared by all the screens. This saves
        /// each screen having to bother creating their own local instance.
        /// </summary>
        public SpriteBatch SpriteBatch
        {
            get { return spriteBatch; }
        }

        /// <summary>
        /// A default font shared by all the screens. This saves
        /// each screen having to bother loading their own local copy.
        /// </summary>
        public SpriteFont Font
        {
            get { return font; }
        }

        #endregion

        protected override object GetService(Type service)
        {
            if (this.game != null)
            {
                object serviceInstance = this.game.Services.GetService(service);
                if (serviceInstance != null)
                {
                    return serviceInstance;
                }
            }
            return base.GetService(service);
        }

        /// <summary>
        /// Initializes the screen manager component.
        /// </summary>
        public override void Initialize()
        {
            if (!this.DesignMode)
            {
                base.Initialize();
            }

            isInitialized = true;
        }

        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            // Load content belonging to the screen manager.

            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = this.Content.Load<SpriteFont>("menufont");
            blankTexture = this.Content.Load<Texture2D>("blank");

            // Tell each of the screens to load their content.
            //foreach (GameScreen screen in screens)
            //{
            //    screen.Initialize();
            //}
        }

        /// <summary>
        /// Unload your graphics content.
        /// </summary>
        protected override void UnloadContent()
        {
            // Tell each of the screens to unload their content.
            //foreach (GameScreen screen in screens)
            //{
            //    screen.UnloadContent();
            //}
        }

        #region Update and Draw

        /// <summary>
        /// Allows each screen to run logic.
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            // Read the keyboard and gamepad.
            input.Update();

            // Make a copy of the master screen list, to avoid confusion if
            // the process of updating one screen adds or removes others.
            screensToUpdate.Clear();
            screensToUpdate.AddRange(screens);

            bool otherScreenHasFocus = !IsActive;
            bool coveredByOtherScreen = false;

            // Loop as long as there are screens waiting to be updated.
            while (screensToUpdate.Count > 0)
            {
                // Pop the topmost screen off the waiting list.
                GameScreen screen = screensToUpdate[screensToUpdate.Count - 1];

                screensToUpdate.RemoveAt(screensToUpdate.Count - 1);

                // Update the screen.
                screen.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

                if (screen.ScreenState == ScreenState.TransitionOn ||
                    screen.ScreenState == ScreenState.Active)
                {
                    // If this is the first active screen we came across,
                    // give it a chance to handle input.
                    if (!otherScreenHasFocus)
                    {
                        screen.HandleInput(input);

                        otherScreenHasFocus = true;
                    }

                    // If this is an active non-popup, inform any subsequent
                    // screens that they are covered by it.
                    if (!screen.IsPopup)
                        coveredByOtherScreen = true;
                }
            }
        }

        /// <summary>
        /// Tells each screen to draw itself.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            foreach (GameScreen screen in screens)
            {
                if (screen.ScreenState == ScreenState.Hidden)
                    continue;

                screen.Draw(gameTime);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds a new screen to the screen manager.
        /// </summary>
        public void AddScreen(GameScreen screen)
        {
            if (screen.BackgroundScreen != null)
            {
                this.AddScreen(screen.BackgroundScreen);
            }
            screen.ScreenManager = this;
            screen.IsExiting = false;

            // If we have a graphics device, tell the screen to load content.
            if (isInitialized)
            {
                screen.Initialize();
            }

            screens.Add(screen);
        }

        /// <summary>
        /// Removes a screen from the screen manager. You should normally
        /// use GameScreen.ExitScreen instead of calling this directly, so
        /// the screen can gradually transition off rather than just being
        /// instantly removed.
        /// </summary>
        public void RemoveScreen(GameScreen screen)
        {
            // If we have a graphics device, tell the screen to unload content.
            if (isInitialized)
            {
                screen.Dispose();
            }

            screens.Remove(screen);
            screensToUpdate.Remove(screen);

            // Also remove the background screen associated with this screen.
            if (screen.BackgroundScreen != null && !screen.BackgroundScreen.IsExiting)
            {
                this.RemoveScreen(screen.BackgroundScreen);
            }
        }

        /// <summary>
        /// Expose an array holding all the screens. We return a copy rather
        /// than the real master list, because screens should only ever be added
        /// or removed using the AddScreen and RemoveScreen methods.
        /// </summary>
        public GameScreen[] GetScreens()
        {
            return screens.ToArray();
        }

        /// <summary>
        /// Helper draws a translucent black fullscreen sprite, used for fading
        /// screens in and out, and for darkening the background behind popups.
        /// </summary>
        public void FadeBackBufferToBlack(int alpha)
        {
            Viewport viewport = GraphicsDevice.Viewport;

            spriteBatch.Begin();

            spriteBatch.Draw(blankTexture,
                             new Rectangle(0, 0, viewport.Width, viewport.Height),
                             new Color(0, 0, 0, (byte)alpha));

            spriteBatch.End();
        }

        public void ResetElapsedTime()
        {
            if (this.game != null)
            {
                this.game.ResetElapsedTime();
            }
        }

        #endregion

        protected override void Dispose(bool disposing)
        {
            if (!this.DesignMode)
            {
                base.Dispose(disposing);
            }
        }
    }
}
